﻿// Odatavalidator ver. 1.0 
// Copyright (c) Microsoft Corporation
// All rights reserved. 

// Licensed under the Apache License, Version 2.0 (the ""License"");
// you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at 
//
//           http://www.apache.org/licenses/LICENSE-2.0 

// THIS CODE IS PROVIDED ON AN  *AS IS* BASIS, WITHOUT WARRANTIES OR 
// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT
// LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS 
// FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. 

// See the Apache Version 2.0 License for specific language governing 
// permissions and limitations under the License.

namespace ODataValidator.Rule.Helper
{
    #region Namespaces
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Xml;
    using System.Xml.Linq;
    using System.Xml.XPath;
    using Newtonsoft.Json.Linq;
    using ODataValidator.RuleEngine;
    using ODataValidator.RuleEngine.Common;
    using System.Net;
    #endregion

    /// <summary>
    /// Helper class to encapsulate metadata parser
    /// </summary>
    public static class MetadataHelper
    {
        /// <summary>
        /// Extension method to convert a string to JSON JObject object
        /// </summary>
        /// <param name="context">The Interop service context</param>
        /// <param name="specifiedPropretyName"></param>
        /// <param name="xpath"></param>
        /// <returns></returns>
        public static List<string> GetPropertyValues(ServiceContext context, string xpath, string specifiedPropretyName)
        {
            List<string> appropriateValues = new List<string>();

            // Use the XPath query language to access the metadata document and get all specified value.
            XElement metadata = XElement.Parse(context.MetadataDocument);
            var properties = metadata.XPathSelectElements(xpath, ODataNamespaceManager.Instance);
            foreach (var property in properties)
            {
                // Whether the specified property exist.
                if (property.Attribute(specifiedPropretyName) != null)
                {
                    // Get the Type attribute value and convert its value to string.
                    string specifiedVlaue = property.Attribute(specifiedPropretyName).Value;
                    appropriateValues.Add(specifiedVlaue);
                }
            }

            return appropriateValues;
        }

        /// <summary>
        /// Get Property Values and Types
        /// </summary>
        /// <param name="PropName">The xmlNode Name</param>
        /// <param name="context">The Interop service context</param>
        /// <param name="PropsType">The Prop Type</param>
        public static bool IsPropsExistInMetadata(string PropName, ServiceContext context, out string PropsType)
        {
            bool isJPropsExistInMetadata = false;
            PropsType = string.Empty;
            List<string> appropriateProperty = new List<string>();
            string primitiveTypeXpath = string.Format(@"//*[local-name()='EntityType' or local-name()='ComplexType'and @Name='{0}']/*[local-name()='Property']", context.EntityTypeShortName);

            // Use the XPath query language to access the metadata document and get all specified value.
            XElement metadata = XElement.Parse(context.MetadataDocument);
            var properties = metadata.XPathSelectElements(primitiveTypeXpath, ODataNamespaceManager.Instance);
            foreach (var property in properties)
            {
                // Whether the specified property exist.
                if (property.Attribute("Type") != null)
                {
                    // Get the Type attribute value and convert its value to string.
                    if (PropName.Equals(property.Attribute("Name").Value))
                    {
                        isJPropsExistInMetadata = true;
                        string specifiedValue = property.Attribute("Type").Value.ToString();
                        PropsType = specifiedValue;
                    }
                }
            }

            return isJPropsExistInMetadata;
        }

        /// <summary>
        /// Get specified Property Name from metadata. 
        /// </summary>
        /// <param name="context">The Interop service context</param>
        /// <param name="xpath">The xpath</param>
        /// <param name="specifiedAttributeName">The specified attribute name</param>
        /// <param name="specifiedAttributeValue">The specified attribute value</param>
        /// <returns></returns>
        public static List<string> GetPropertyName(ServiceContext context, string xpath, string specifiedAttributeName, string specifiedAttributeValue)
        {
            List<string> appropriateValues = new List<string>();

            // Use the XPath query language to access the metadata document and get all specified value.
            XElement metadata = XElement.Parse(context.MetadataDocument);
            var properties = metadata.XPathSelectElements(xpath, ODataNamespaceManager.Instance);
            foreach (var property in properties)
            {
                // Whether the specified property exist.
                if (property.Attribute(specifiedAttributeName) != null && property.Attribute(specifiedAttributeName).Value.Equals(specifiedAttributeValue))
                {
                    // Get the Type attribute value and convert its value to string.
                    string specifiedVlaue = property.Attribute("Name").Value;
                    appropriateValues.Add(specifiedVlaue);
                }
            }

            return appropriateValues;
        }

        /// <summary>
        /// Gets all the complex name from metadata.
        /// </summary>
        /// <param name="metadata">Store the metadata information.</param>
        /// <returns>Returns all the complex names from the metadata.</returns>
        public static List<string> GetAllComplexNameFromMetadata(string metadata)
        {
            List<string> result = new List<string>();

            if (metadata == null || metadata == string.Empty)
            {
                return result;
            }

            XElement metadataRoot = XElement.Parse(metadata);
            string xPath = @"//*[local-name()='ComplexType']";
            IEnumerable<XElement> complexTypes = metadataRoot.XPathSelectElements(xPath, ODataNamespaceManager.Instance);

            foreach (var complexType in complexTypes)
            {
                result.Add(complexType.Attribute("Name").Value);
            }

            return result;
        }

        /// <summary>
        /// Get specified properties with complex type.
        /// </summary>
        /// <param name="metadataDoc">The metadata document.</param>
        /// <param name="entityTypeShortName">The short name of entity type.</param>
        /// <param name="isCollection">Indicates whether the type is a collection or not. 
        /// Collection: true.
        /// Non collection: false.
        /// Both: null</param>
        /// <returns>Returns all the appropriate properties with complex type.</returns>
        public static IEnumerable<XElement> GetComplexTypeProperties(string metadataDoc, string entityTypeShortName, bool? isCollection)
        {
            if (!metadataDoc.IsXmlPayload() || string.IsNullOrEmpty(entityTypeShortName))
            {
                return new List<XElement>();
            }

            List<XElement> result = new List<XElement>();
            var complexTypes = MetadataHelper.GetAllComplexNameFromMetadata(metadataDoc);

            XElement metadata = XElement.Parse(metadataDoc);
            string xPath = string.Format("//*[local-name()='EntityType' and @Name='{0}']/*[local-name()='Property']", entityTypeShortName);
            var props = metadata.XPathSelectElements(xPath, ODataNamespaceManager.Instance);

            if (null != props)
            {
                foreach (var p in props)
                {
                    if (null != p.Attribute("Type"))
                    {
                        var originalPropType = p.Attribute("Type").Value;
                        var propType = originalPropType.RemoveCollectionFlag().GetLastSegment();

                        if (complexTypes.Contains(propType))
                        {
                            bool? flag = null;
                            if (originalPropType.StartsWith("Collection("))
                            {
                                flag = true;
                            }
                            else if (!originalPropType.Contains("Collection"))
                            {
                                flag = false;
                            }

                            if (flag == isCollection)
                            {
                                result.Add(p);
                            }
                        }
                    }
                }
            }

            return result;
        }
            
        /// <summary>
        /// Judge whether the entity is media type
        /// </summary>
        /// <param name="metadata">The metadata document.</param>
        /// <param name="entityTypeShortName">The name of entity type.</param>
        /// <returns> List of result </returns>
        public static bool IsMediaEntity(string metadata, string entityTypeShortName)
        {
            string xpath1 = string.Format(@"//*[local-name()='EntityType' and @Name='{0}']", entityTypeShortName);
            XElement md = XElement.Parse(metadata);
            XElement entity = md.XPathSelectElement(xpath1, ODataNamespaceManager.Instance);
            string HasStream = entity.GetAttributeValue(@"HasStream");

            if (HasStream != null && HasStream.ToLower() == "true")
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// get all streamtype element from an Entry and ComplexType metadata. BaseClass will be searched
        /// </summary>
        /// <param name="metadata">The metadata document.</param>
        /// <param name="entityTypeShortName">The name of entity type.</param>
        /// <returns> List of result </returns>
        public static List<XElement> GetAllStreamTypeFromEntry(string metadata, string entityTypeShortName)
        {
            var complexTypeNames = MetadataHelper.GetAllComplexNameFromMetadata(metadata);

            Queue<XElement> complexTypeEnumQueue = new Queue<XElement>();
            List<XElement> streamTypeResultList = new List<XElement>();

            string xpath1 = string.Format(@"//*[local-name()='EntityType' and @Name='{0}']", entityTypeShortName);
            XElement md = XElement.Parse(metadata);
            XElement entity = md.XPathSelectElement(xpath1, ODataNamespaceManager.Instance);
            complexTypeEnumQueue.Enqueue(entity);

            while (complexTypeEnumQueue.Count != 0)
            {
                XElement complexTypeOpen = complexTypeEnumQueue.Dequeue();

                string baseTypeFullName = entity.Attribute(@"BaseType") != null ? entity.Attribute(@"BaseType").Value : string.Empty;

                if (baseTypeFullName != string.Empty)
                {
                    string ns = entity.Parent.Attribute("Namespace").Value;
                    string baseTypeShortName =
                        baseTypeFullName.Contains(@".") ?
                        baseTypeFullName.Remove(0, ns.Length + 1) :
                        baseTypeFullName;

                    xpath1 = string.Format(@"//*[local-name()='{0}' and @Name='{1}']", complexTypeOpen.Name.LocalName, baseTypeFullName);

                    var baseXElement = md.XPathSelectElement(xpath1, ODataNamespaceManager.Instance);
                    complexTypeEnumQueue.Enqueue(baseXElement);
                }

                IEnumerable<XElement> openedResult = null;

                if (complexTypeOpen.Name.LocalName == "EntityType")
                {
                    openedResult = MetadataHelper.GetAllPropertiesOfEntity(metadata, complexTypeOpen.GetAttributeValue("Name"), MatchPropertyType.Normal);
                }
                else
                {
                    string xpath2 = string.Format(@"//*[local-name()='ComplexType' and @Name='{0}']/*[local-name()='Property']", complexTypeOpen.GetAttributeValue("Name"));
                    openedResult = complexTypeOpen.XPathSelectElements(xpath2, ODataNamespaceManager.Instance);
                }

                foreach (XElement xe in openedResult)
                {
                    string type = xe.GetAttributeValue("Type");
                    if (type == "Edm.Stream")
                    {
                        streamTypeResultList.Add(xe);
                        continue;
                    }
                    
                    if (type != null && complexTypeNames.Contains( type.GetLastSegment().Trim(')')))
                    {
                        string xpath2 = string.Format(@"//*[local-name()='ComplexType' and @Name='{0}']", xe.GetAttributeValue("Name"));
                        var complexTypeElement = complexTypeOpen.XPathSelectElement(xpath2, ODataNamespaceManager.Instance);
                        complexTypeEnumQueue.Enqueue(complexTypeElement);
                    }
                }
            }

            return streamTypeResultList;
        }

        /// <summary>
        /// Get the names of complex type of normal property in specified entity.
        /// </summary>
        /// <param name="entityTypeShortName">The name of entity type.</param>
        /// <param name="metadata">The metadata document.</param>
        /// <param name="complexTypeNames">The specified complex types.</param>
        /// <returns></returns>
        public static Dictionary<string, string> GetPropertyNameWithComplexTypeFromEntity(string entityTypeShortName, string metadata, List<string> complexTypeNames)
        {
            Dictionary<string, string> propertiesNameWithComplexType = new Dictionary<string, string>();

            List<XElement> propertiesWithComplexType = GetPropertyElementWithComplexType(entityTypeShortName, metadata, complexTypeNames);

            foreach (XElement xe in propertiesWithComplexType)
            {
                propertiesNameWithComplexType.Add(xe.GetAttributeValue("Name"), xe.GetAttributeValue("Type").GetLastSegment().Trim(')'));
            }

            return propertiesNameWithComplexType;
        }

        /// <summary>
        /// Get complex type of normal property element in specified entity.
        /// </summary>
        /// <param name="entityTypeShortName">The name of entity type.</param>
        /// <param name="metadata">The metadata document.</param>
        /// <param name="complexTypeNames">The specified complex types.</param>
        /// <returns></returns>
        public static List<XElement> GetPropertyElementWithComplexType(string entityTypeShortName, string metadata, List<string> complexTypeNames)
        {
            List<XElement> propertiesWithComplexType = new List<XElement>();

            List<XElement> normalProperties = GetAllPropertiesOfEntity(metadata, entityTypeShortName, MatchPropertyType.Normal);

            foreach (XElement xe in normalProperties)
            {
                if (xe.GetAttributeValue("Type") != null && complexTypeNames.Contains(xe.GetAttributeValue("Type").GetLastSegment().Trim(')')))
                {
                    propertiesWithComplexType.Add(xe);
                }
            }

            return propertiesWithComplexType;
        }

        /// <summary>
        /// Gets the type of property's value from the metadata.
        /// </summary>
        /// <param name="propName">Indicate the property's name.</param>
        /// <param name="props">Indicate the properties.</param>
        /// <returns>Returns the value of property's type.</returns>
        public static string GetPropertyValueTypeFromMetadata(string propName, IEnumerable<XElement> props)
        {
            if (propName == string.Empty || propName == null || props == null)
            {
                return null;
            }

            string result = string.Empty;

            foreach (var prop in props)
            {
                if (propName == prop.Attribute("Name").Value)
                {
                    result = prop.Attribute("Type").Value;
                    break;
                }
            }

            return result;
        }

        /// <summary>
        /// Gets property type from metadata.
        /// </summary>
        /// <param name="propName">Indicate the property's name.</param>
        /// <param name="entityTypeShortName">Indicate the entity type's short name.</param>
        /// <param name="metadata">Store the metadata information.</param>
        /// <returns>Returns the property type.</returns>
        public static string GetPropertyTypeFromMetadata(string propName, string entityTypeShortName, string metadata)
        {
            string propType = null;

            if (string.IsNullOrEmpty(propName) || string.IsNullOrEmpty(entityTypeShortName) || !metadata.IsXmlPayload())
            {
                return propType;
            }

            IEnumerable<XElement> props = GetAllPropertiesOfEntity(metadata, entityTypeShortName, MatchPropertyType.Normal);

            foreach (var prop in props)
            {
                if (propName == prop.Attribute("Name").Value)
                {
                    propType = prop.Attribute("Type").Value;
                    break;
                }
            }

            return propType;
        }

        /// <summary>
        /// Gets navigation property type from metadata.
        /// </summary>
        /// <param name="navigPropName">Indicate the navigation property's name.</param>
        /// <param name="entityTypeShortName">Indicate the entity type's short name.</param>
        /// <param name="metadata">Store the metadata information.</param>
        /// <returns>Returns the navigation property type.</returns>
        public static string GetNavigPropertyTypeFromMetadata(string navigPropName, string entityTypeShortName, string metadata)
        {
            string navigPropType = null;

            if (string.IsNullOrEmpty(navigPropName) || string.IsNullOrEmpty(entityTypeShortName) || !metadata.IsXmlPayload())
            {
                return navigPropType;
            }

            IEnumerable<XElement> props = GetAllPropertiesOfEntity(metadata, entityTypeShortName, MatchPropertyType.Navigations);

            foreach (var prop in props)
            {
                if (navigPropName == prop.Attribute("Name").Value)
                {
                    navigPropType = prop.Attribute("Type").Value;
                    break;
                }
            }

            return navigPropType;
        }

        /// <summary>
        /// Get the names of appropriate navigation properties from metadata.
        /// </summary>
        /// <param name="sc">The service context.</param>
        /// <param name="type">The navigation property type.</param>
        /// <returns>Returns the list of navigation property names.</returns>
        public static List<string> GetAppropriateNavigationPropertyNames(ServiceContext sc, NavigationPropertyType type)
        {
            if (sc == null)
            {
                return null;
            }

            List<string> result = new List<string>();
            string xpath = string.Format("//*[local-name()='EntityType' and @Name='{0}']/*[local-name()='NavigationProperty']", sc.EntityTypeShortName);
            XElement metadata = XElement.Parse(sc.MetadataDocument);
            var navigProps = metadata.XPathSelectElements(xpath, ODataNamespaceManager.Instance);

            if (ODataVersion.V4 == sc.Version)
            {
                var collectionTypeNavigProps = NavigationPropertyType.SetOfEntities == type ?
                    navigProps.Where(np => np.Attribute(@"Type").Value.Contains(@"Collection")) :
                    navigProps.Where(np => !np.Attribute(@"Type").Value.Contains(@"Collection"));

                foreach (var ctnp in collectionTypeNavigProps)
                {
                    result.Add(ctnp.Attribute(@"Name").Value);
                }
            }
            else
            {
                string typeFlag = NavigationPropertyType.SetOfEntities == type ? @"*" : @"0..1";
                xpath = string.Format("//*[local-name()='EntityType' and @Name='{0}']", sc.EntityTypeShortName);
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.LoadXml(sc.MetadataDocument);
                XmlNode entityTypeNode = xmlDoc.SelectSingleNode(xpath, ODataNamespaceManager.Instance);
                string odataNamespace = string.Empty;

                if (null != entityTypeNode.ParentNode.Attributes[@"Namespace"])
                {
                    odataNamespace = entityTypeNode.ParentNode.Attributes[@"Namespace"].Value;
                }

                string associNameFilter = @"1!=1";
                string toRoleNameFilter = @"1!=1";

                foreach (var np in navigProps)
                {
                    string associName = np.Attribute(@"Relationship").Value.Remove(0, (odataNamespace + @".").Length);
                    associNameFilter += @" or " + string.Format(@"@Name='{0}'", associName);

                    string toRoleName = np.Attribute(@"ToRole").Value;
                    toRoleNameFilter += @" or " + string.Format(@"@Role='{0}'", toRoleName);
                }

                xpath = string.Format(@"//*[local-name()='Association'][{0}]/*[local-name()='End'][@Multiplicity='{1}' and ({2})]", associNameFilter, typeFlag, toRoleNameFilter);
                XmlNodeList endNodes = xmlDoc.SelectNodes(xpath, ODataNamespaceManager.Instance);
                List<string> toRoleNames = new List<string>();

                foreach (XmlNode en in endNodes)
                {
                    toRoleNames.Add(en.Attributes[@"Role"].Value);
                }

                foreach (var np in navigProps)
                {
                    if (toRoleNames.Contains(np.Attribute(@"ToRole").Value))
                    {
                        result.Add(np.Attribute(@"Name").Value);
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// Get the names of appropriate navigation properties from metadata.
        /// </summary>
        /// <param name="entityTypeShortName">The entity type short name.</param>
        /// <param name="metadataDoc">The metadata document.</param>
        /// <param name="type">The navigation property rough type.</param>
        /// <returns>Returns the list of navigation property names.</returns>
        public static List<string> GetAppropriateNavigationPropertyNames(string entityTypeShortName, string metadataDoc, NavigationRoughType type)
        {
            if (string.IsNullOrEmpty(entityTypeShortName) || string.IsNullOrEmpty(metadataDoc))
            {
                return null;
            }

            List<string> result = new List<string>();
            string xpath = string.Format("//*[local-name()='EntityType' and @Name='{0}']/*[local-name()='NavigationProperty']", entityTypeShortName);
            XElement metadata = XElement.Parse(metadataDoc);
            var navigProps = metadata.XPathSelectElements(xpath, ODataNamespaceManager.Instance);

            if (null == navigProps || !navigProps.Any())
            {
                return null;
            }

            foreach (var np in navigProps)
            {
                if (null == np.Attribute("Name") || null == np.Attribute("Type"))
                {
                    break;
                }

                if (NavigationRoughType.None == type)
                {
                    result.Add(np.GetAttributeValue("Name"));
                }
                else if (NavigationRoughType.SingleValued == type)
                {
                    if (!np.GetAttributeValue("Type").StartsWith("Collection("))
                    {
                        result.Add(np.GetAttributeValue("Name"));
                    }
                }
                else
                {
                    if (np.GetAttributeValue("Type").StartsWith("Collection("))
                    {
                        result.Add(np.GetAttributeValue("Name"));
                    }
                }
            }

            return result;
        }

        public static Dictionary<string, string> GetAppropriateNavigationPropertyNameAndType(string entityTypeName, string metadataDocument, NavigationPropertyType type)
        {
            Dictionary<string, string> result = new Dictionary<string, string>();
            string xpath = string.Format("//*[local-name()='EntityType' and @Name='{0}']/*[local-name()='NavigationProperty']", entityTypeName);
            XElement metadata = XElement.Parse(metadataDocument);
            var navigProps = metadata.XPathSelectElements(xpath, ODataNamespaceManager.Instance);

            var collectionTypeNavigProps = NavigationPropertyType.SetOfEntities == type ?
                navigProps.Where(np => np.Attribute(@"Type").Value.Contains(@"Collection")) :
                navigProps.Where(np => !np.Attribute(@"Type").Value.Contains(@"Collection"));

            foreach (var ctnp in collectionTypeNavigProps)
            {
                result.Add(ctnp.GetAttributeValue("Name"), ctnp.GetAttributeValue("Type"));
            }

            return result;
        }

        /// <summary>
        /// Get first group of navigation property name recursing by system query $levels value. 
        /// (Note: Only support V4 service)
        /// </summary>
        /// <param name="entityTypeShortName">The entity type short name.</param>
        /// <param name="metadataDoc">The metadata document.</param>
        /// <param name="levels">The value of system query $levels.</param>
        /// <returns></returns>
        public static string[] GetNavigPropNamesRecurseByLevels(this string entityTypeShortName, string metadataDoc, int levels)
        {
            string[] result = new string[levels];

            if (string.IsNullOrEmpty(entityTypeShortName) || string.IsNullOrEmpty(metadataDoc))
            {
                return result;
            }

            for (int i = 0; i < levels; i++)
            {
                string xpath = string.Format("//*[local-name()='EntityType' and @Name='{0}']/*[local-name()='NavigationProperty']", entityTypeShortName);
                XElement metadata = XElement.Parse(metadataDoc);
                var navigProp = metadata.XPathSelectElement(xpath, ODataNamespaceManager.Instance);

                if (null == navigProp || null == navigProp.Attribute("Name"))
                {
                    return result;
                }

                result[i] = navigProp.Attribute(@"Name").Value;
                string navigPropType = navigProp.GetAttributeValue("Type");
                entityTypeShortName = navigPropType.Contains(@"Collection") ?
                    navigPropType.RemoveCollectionFlag().GetLastSegment() :
                    navigPropType.GetLastSegment();
            }

            return result;
        }

        /// <summary>
        /// Get all the properties from EntityType element in metadata.
        /// </summary>
        /// <param name="metadata">The whole metadata data.</param>
        /// <param name="entityTypeShortName">The short name of entity type.</param>
        /// <param name="mpType">The matching property type.</param>
        /// <returns>Returns all the properties of an entity.</returns>
        public static List<XElement> GetAllPropertiesOfEntity(string metadata, string entityTypeShortName, MatchPropertyType mpType)
        {
            if (metadata == null || metadata == string.Empty || entityTypeShortName == null || entityTypeShortName == string.Empty)
            {
                return null;
            }

            string xpath1 = string.Format(@"//*[local-name()='EntityType' and @Name='{0}']", entityTypeShortName);
            string xpath2 = string.Empty;
            XElement md = XElement.Parse(metadata);
            var entity = md.XPathSelectElement(xpath1, ODataNamespaceManager.Instance);

            if (entity != null)
            {
                string baseTypeFullName = entity.Attribute(@"BaseType") != null ? entity.Attribute(@"BaseType").Value : string.Empty;
                string ns = entity.Parent.Attribute("Namespace").Value;
                string baseTypeShortName =
                    baseTypeFullName.Contains(@".") ?
                    baseTypeFullName.Remove(0, ns.Length + 1) :
                    baseTypeFullName;

                switch (mpType)
                {
                    default:
                    case MatchPropertyType.Normal:
                        xpath1 = @"./*[local-name()='Property']";
                        xpath2 = string.Format(@"//*[local-name()='EntityType' and @Name='{0}']/*[local-name()='Property']", baseTypeShortName);
                        break;
                    case MatchPropertyType.Navigations:
                        xpath1 = @"./*[local-name()='NavigationProperty']";
                        xpath2 = string.Format(@"//*[local-name()='EntityType' and @Name='{0}']/*[local-name()='NavigationProperty']", baseTypeShortName);
                        break;
                    case MatchPropertyType.All:
                        xpath1 = @"./*[local-name()='Property' or local-name()='NavigationProperty']";
                        xpath2 = string.Format(@"//*[local-name()='EntityType' and @Name='{0}']/*[local-name()='Property' or local-name()='NavigationProperty']", baseTypeShortName);
                        break;
                }

                var currentEntityProps = entity.XPathSelectElements(xpath1, ODataNamespaceManager.Instance);
                var baseEntityProps = baseTypeFullName != string.Empty ? md.XPathSelectElements(xpath2, ODataNamespaceManager.Instance) : null;

                return baseEntityProps != null ? baseEntityProps.Concat(currentEntityProps).ToList() : currentEntityProps.ToList();
            }
            else
            {
                return new List<XElement>();
            }
        }

        /// <summary>
        /// Get property names of complex type.
        /// </summary>
        /// <param name="metadata">The metadata document.</param>
        /// <param name="complexName">The name of complex type.</param>
        /// <returns>The list of property names.</returns>
        public static List<string> GetAllPropertiesNamesOfComplexType(string metadata, string complexName)
        {
            if (string.IsNullOrEmpty(metadata) || string.IsNullOrEmpty(complexName))
            {
                return null;
            }

            List<string> propertiesNames = new List<string>();
            string xpath = string.Format(@"//*[local-name()='ComplexType' and @Name='{0}']", complexName);
            //string xpath = string.Format(@"//*[local-name()='ComplexType' and @Name='{0}']/*[local-name()='Property']", complexName);
            XElement md = XElement.Parse(metadata);
            var complexType = md.XPathSelectElement(xpath, ODataNamespaceManager.Instance);

            if (complexType != null)
            {
                string baseTypeName = complexType.GetAttributeValue(@"BaseType") != null ? complexType.GetAttributeValue(@"BaseType").GetLastSegment() : string.Empty;
                string xpath1 = @"./*[local-name()='Property']";
                string xpath2 = string.Format(@"//*[local-name()='ComplexType' and @Name='{0}']/*[local-name()='Property']", baseTypeName);

                var currentComplexTypeProperties = complexType.XPathSelectElements(xpath1, ODataNamespaceManager.Instance);
                var baseComplexTypeProperties = !string.IsNullOrEmpty(baseTypeName) ? md.XPathSelectElements(xpath2, ODataNamespaceManager.Instance) : null;

                var total = baseComplexTypeProperties != null ? currentComplexTypeProperties.Concat(baseComplexTypeProperties) : currentComplexTypeProperties;
                foreach (XElement xe in total)
                {
                    propertiesNames.Add(xe.GetAttributeValue("Name"));
                }
            }

            return propertiesNames;
        }

        /// <summary>
        /// Get property names and types of a complex type.
        /// </summary>
        /// <param name="metadata">The metadata document.</param>
        /// <param name="complexName">The name of complex type.</param>
        /// <returns>The dictionay of property name and its type.</returns>
        public static Dictionary<string, string> GetAllPropertiesOfComplexType(string metadata, string complexName)
        {
            if (string.IsNullOrEmpty(metadata) || string.IsNullOrEmpty(complexName))
            {
                return null;
            }

            Dictionary<string, string> properties = new Dictionary<string, string>();
            string xpath = string.Format(@"//*[local-name()='ComplexType' and @Name='{0}']", complexName);
            XElement md = XElement.Parse(metadata);
            var complexType = md.XPathSelectElement(xpath, ODataNamespaceManager.Instance);

            if (complexType != null)
            {
                string baseTypeName = complexType.GetAttributeValue(@"BaseType") != null ? complexType.GetAttributeValue(@"BaseType").GetLastSegment() : string.Empty;
                string xpath1 = @"./*[local-name()='Property']";
                string xpath2 = string.Format(@"//*[local-name()='ComplexType' and @Name='{0}']/*[local-name()='Property']", baseTypeName);

                var currentComplexTypeProperties = complexType.XPathSelectElements(xpath1, ODataNamespaceManager.Instance);
                var baseComplexTypeProperties = !string.IsNullOrEmpty(baseTypeName) ? md.XPathSelectElements(xpath2, ODataNamespaceManager.Instance) : null;

                var total = baseComplexTypeProperties != null ? currentComplexTypeProperties.Concat(baseComplexTypeProperties) : currentComplexTypeProperties;
                foreach (XElement xe in total)
                {
                    properties.Add(xe.GetAttributeValue("Name"), xe.GetAttributeValue("Type"));
                }
            }

            return properties;
        }

        /// <summary>
        /// Get the sorted properties from EntityType elements in metadata.
        /// </summary>
        /// <param name="metadata">The whole metadata data.</param>
        /// <param name="entityTypeShortName">The short name of entity type.</param>
        /// <returns>Returns properties' name and type.</returns>
        public static List<string> GetSortedPropertiesOfEntity(string metadata, string entityTypeShortName)
        {
            if (string.IsNullOrEmpty(metadata) && string.IsNullOrEmpty(entityTypeShortName))
            {
                return null;
            }

            List<string> result = new List<string>();
            var props = GetAllPropertiesOfEntity(metadata, entityTypeShortName, MatchPropertyType.Normal);
            props.FindAll(p => IsSortedPrimitiveDataType(p.Attribute("Type").Value)).ForEach(p => { result.Add(string.Format(@"{0},{1}", p.Attribute("Name").Value, p.Attribute("Type").Value)); });
            return result;
        }

        /// <summary>
        /// Get the normal properties' names from EntityType elements in metadata.
        /// </summary>
        /// <param name="metadata">The metadata document.</param>
        /// <param name="entityTypeShortName">The entity-type short name.</param>
        /// <returns>Returns the properties' name.</returns>
        public static List<string> GetNormalPropertiesNames(string metadata, string entityTypeShortName)
        {
            List<string> result = new List<string>();
            var props = GetAllPropertiesOfEntity(metadata, entityTypeShortName, MatchPropertyType.Normal);

            if (props.Any())
            {
                foreach (XElement xe in props)
                {
                    result.Add(xe.Attribute("Name").Value);
                }
            }

            return result;
        }

        /// <summary>
        /// Get the navigation properties' names from EntityType elements in metadata.
        /// </summary>
        /// <param name="metadata">The metadata docment.</param>
        /// <param name="entityTypeShortName">The entity-type short name.</param>
        /// <returns>Returns the navigation properties' name.</returns>
        public static List<string> GetNavigationPropertiesNames(string metadata, string entityTypeShortName)
        {
            List<string> result = new List<string>();
            var props = GetAllPropertiesOfEntity(metadata, entityTypeShortName, MatchPropertyType.Navigations);

            if (props.Any())
            {
                foreach (XElement xe in props)
                {
                    result.Add(xe.Attribute("Name").Value);
                }
            }

            return result;
        }

        /// <summary>
        /// Judge the primitive data type whether can be sorted or not.
        /// </summary>
        /// <param name="primitiveDataType">The primitive data type name.</param>
        /// <returns>Returns the result.</returns>
        public static bool IsSortedPrimitiveDataType(string primitiveDataType)
        {
            return PrimitiveDataTypes.Binary == primitiveDataType ||
                PrimitiveDataTypes.Boolean == primitiveDataType ||
                PrimitiveDataTypes.Byte == primitiveDataType ||
                PrimitiveDataTypes.Decimal == primitiveDataType ||
                PrimitiveDataTypes.Double == primitiveDataType ||
                PrimitiveDataTypes.Guid == primitiveDataType ||
                PrimitiveDataTypes.Int16 == primitiveDataType ||
                PrimitiveDataTypes.Int32 == primitiveDataType ||
                PrimitiveDataTypes.Int64 == primitiveDataType ||
                PrimitiveDataTypes.SByte == primitiveDataType ||
                PrimitiveDataTypes.Single == primitiveDataType ||
                PrimitiveDataTypes.String == primitiveDataType ?
                true : false;
        }

        /// <summary>
        /// Gets the name of the first entity set which contains entities with at least one navigation- and collection-valued property from metadata.
        /// </summary>
        /// <param name="metadataDoc">Metadata document.</param>
        /// <param name="navigationCollectionValuedPropertyName">Outputs the name of navigation- and collection-valued property.</param>
        /// <returns>Returns the appropriate entity set's name.</returns>
        public static string GetNameOfFirstEntitySetContainsNavigationCollectionValuedProperty(string metadataDoc, out string navigationCollectionValuedPropertyName, out string navigationCollectionValuedPropertyEntityType)
        {
            string result = string.Empty;
            string odataNamespace = string.Empty;
            string entityTypeName = string.Empty;
            string collectionEntityType = string.Empty;
            navigationCollectionValuedPropertyName = string.Empty;
            navigationCollectionValuedPropertyEntityType = string.Empty;

            if (string.IsNullOrEmpty(metadataDoc))
            {
                return string.Empty;
            }

            string xpath = @"//*[local-name()='EntityType']";
            XElement metadata = XElement.Parse(metadataDoc);
            var entityTypes = metadata.XPathSelectElements(xpath, ODataNamespaceManager.Instance);

            foreach (var entityType in entityTypes)
            {
                xpath = @"./*[local-name()='NavigationProperty']";
                var navigProps = entityType.XPathSelectElements(xpath, ODataNamespaceManager.Instance);

                foreach (var navigProp in navigProps)
                {
                    if (navigProp.Attribute("Type").Value.Contains("Collection"))
                    {
                        navigationCollectionValuedPropertyName = navigProp.Attribute("Name").Value;
                        string type = navigProp.GetAttributeValue("Type");
                        collectionEntityType = type.Substring(type.IndexOf('(') + 1, type.LastIndexOf(')') - type.IndexOf('(') - 1);
                        break;
                    }
                }

                if (string.Empty != navigationCollectionValuedPropertyName)
                {
                    entityTypeName = entityType.Attribute("Name").Value;
                    odataNamespace = entityType.Parent.Attribute("Namespace").Value;

                    if (collectionEntityType.Contains(odataNamespace))
                    {
                        navigationCollectionValuedPropertyEntityType = collectionEntityType.Remove(0, odataNamespace.Length + 1);
                    }
                    else
                    {
                        navigationCollectionValuedPropertyEntityType = collectionEntityType;
                    }

                    break;
                }
            }

            if (string.Empty != entityTypeName && string.Empty != odataNamespace && string.Empty != navigationCollectionValuedPropertyName)
            {
                xpath = string.Format(@"//*[local-name()='EntityContainer']/*[local-name()='EntitySet' and @EntityType='{0}']", string.Format("{0}.{1}", odataNamespace, entityTypeName));
                result = metadata.XPathSelectElement(xpath, ODataNamespaceManager.Instance).Attribute("Name").Value;
            }

            return result;
        }

        /// <summary>
        /// Gets all the non-navigation properties with special type from an entity type element in metadata document.
        /// </summary>
        /// <param name="entityTypeName">The name of an entity type.</param>
        /// <param name="metadataDoc">The metadata document.</param>
        /// <param name="primitiveTypes">The specified type to limit the output properties.</param>
        /// <returns>Returns all the properties with specified type.</returns>
        public static List<string> GetPropertiesWithSpecifiedTypeFromEntityType(string entityTypeName, string metadataDoc, List<string> primitiveTypes)
        {
            List<string> result = new List<string>();

            if (string.IsNullOrEmpty(entityTypeName) || string.IsNullOrEmpty(metadataDoc) || primitiveTypes.Count == 0)
            {
                return result;
            }

            string xpath = string.Format(@"//*[local-name()='EntityType' and @Name='{0}']/*[local-name()='Property']", entityTypeName);
            XElement metadata = XElement.Parse(metadataDoc);
            var props = metadata.XPathSelectElements(xpath, ODataNamespaceManager.Instance).ToList();
            result = props.Where(p => primitiveTypes.Contains(p.Attribute("Type").Value)).Select(p => string.Format(@"{0},{1}", p.Attribute("Name").Value, p.Attribute("Type").Value)).ToList();

            return result;
        }

        /// <summary>
        /// Get the URLs of entity sets which have a specified navigation property.
        /// </summary>
        /// <param name="sc">The service context.</param>
        /// <param name="navigPropType">The type of navigation property.</param>
        /// <returns>Returns a list contatins all the appropriate entity set URLs.</returns>
        public static List<string> GetEntitySetUrlsContainsCurrentEntityTypeName(ServiceContext sc, NavigationPropertyType navigPropType)
        {
            if (sc == null)
            {
                return new List<string>();
            }

            string entityTypeNs = sc.EntityTypeFullName.RemoveEnd(sc.EntityTypeShortName);
            string target = NavigationPropertyType.SetOfEntities == navigPropType ?
                            string.Format("Collection({0})", sc.EntityTypeFullName) :
                            sc.EntityTypeFullName;
            string xpath = string.Format("//*[local-name()='EntityType']/*[local-name()='NavigationProperty' and @Type='{0}']", target);
            XElement metadata = XElement.Parse(sc.MetadataDocument);
            var navigProps = metadata.XPathSelectElements(xpath, ODataNamespaceManager.Instance).ToList();

            if (navigProps == null)
            {
                return new List<string>();
            }

            var entityTypeElementNames = new List<string>();
            navigProps.ForEach(n => entityTypeElementNames.Add(entityTypeNs + n.Parent.Attribute("Name").Value));

            xpath = @"//*[local-name()='EntityContainer']/*[local-name()='EntitySet']";
            var allEntitySetElements = metadata.XPathSelectElements(xpath, ODataNamespaceManager.Instance).ToList();

            if (allEntitySetElements == null)
            {
                return new List<string>();
            }

            var entitySetElementNames = allEntitySetElements
                .FindAll(entitySet => entityTypeElementNames.Contains(entitySet.Attribute("EntityType").Value))
                .Select(entitySet => entitySet.Attribute("Name").Value);

            JObject service = JObject.Parse(sc.ServiceDocument);
            var entities = JsonParserHelper.GetEntries(service).ToList();

            if (entities == null)
            {
                return new List<string>();
            }

            return entities.Where(e => entitySetElementNames.Contains(e["name"].ToString())).Select(e => e["url"].ToString()).ToList();
        }

        /// <summary>
        /// Gets inheritance of entity types from metadata.
        /// </summary>
        /// <param name="metadataDoc">The metadata document.</param>
        /// <returns>Returns a list of entity type with inheritance.</returns>
        public static List<EntityTypeElement> GetEntityTypeInheritance(string metadataDoc, string serviceDoc)
        {
            List<EntityTypeElement> result = new List<EntityTypeElement>();

            if (string.IsNullOrEmpty(metadataDoc) || string.IsNullOrEmpty(serviceDoc))
            {
                return result;
            }

            XElement metadata = XElement.Parse(metadataDoc);
            string xpath = @"//*[local-name()='Schema']/*[local-name()='EntityType']";
            metadata.XPathSelectElements(xpath, ODataNamespaceManager.Instance)
                .ToList()
                .ForEach(r =>
                {
                    if (null != r.Attribute("BaseType"))
                    {
                        result.Add(EntityTypeElement.Parse(r));
                    }
                });

            return result;
        }

        /// <summary>
        /// Gets the name of entity type which contains specified navigation property.
        /// </summary>
        /// <param name="navigPropType">The navigation property's name.</param>
        /// <param name="metadataDoc">The metadata document.</param>
        /// <returns>Returns a list of entity type full name.</returns>
        public static List<string> GetEntityTypeNamesContainsSpecifiedNavigProp(string navigPropType, string metadataDoc)
        {
            List<string> result = new List<string>();

            if (string.IsNullOrEmpty(navigPropType) || !metadataDoc.IsXmlPayload())
            {
                return result;
            }

            XElement metadata = XElement.Parse(metadataDoc);
            string xpath = string.Format(@"//*[local-name()='EntityType']/*[local-name()='NavigationProperty' and @Type='{0}']", navigPropType);
            metadata.XPathSelectElements(xpath, ODataNamespaceManager.Instance)
                .ToList()
                .ForEach(r =>
                {
                    if (r.Parent != null && r.Parent.Attribute("Name") != null)
                    {
                        string ns = r.Parent.Parent != null && r.Parent.Parent.Attribute("Namespace") != null ?
                            r.Parent.Parent.Attribute("Namespace").Value :
                            string.Empty;
                        result.Add(string.Format("{0}.{1}", ns, r.Parent.Attribute("Name").Value));
                    }
                });

            return result;
        }

        #region Convert to each others among entity-type short name, entity-type full name, entity-set name, entity-set URL.
        /// <summary>
        /// Convert an entity-type name to its entity-set url.
        /// </summary>
        /// <param name="entityTypeName">An entity-type name.</param>
        /// <param name="metadataDoc">The metadata document.</param>
        /// <param name="serviceDoc">The service document.</param>
        /// <returns>Returns an entity-set url.</returns>
        public static string ConvertToEntitySetUrl(this string entityTypeName, string metadataDoc, string serviceDoc)
        {
            if (string.IsNullOrEmpty(entityTypeName) || string.IsNullOrEmpty(metadataDoc) || string.IsNullOrEmpty(serviceDoc))
            {
                return string.Empty;
            }

            string entityTypeFullName = entityTypeName.AddNamespace(AppliesToType.EntityType, metadataDoc);
            var entitySetName = ConvertToEntitySetNameUsingMetadataDoc(entityTypeFullName, metadataDoc);

            return entitySetName.ConvertToEntitySetUrl(serviceDoc);
        }

        /// <summary>
        /// Convert an entity-set name to its url through the service document.
        /// </summary>
        /// <param name="entitySetName">An entity-set name.</param>
        /// <param name="serviceDoc">The service document.</param>
        /// <returns>Returns the related entity-set url.</returns>
        public static string ConvertToEntitySetUrl(this string entitySetName, string serviceDoc)
        {
            if (string.IsNullOrEmpty(entitySetName) || string.IsNullOrEmpty(serviceDoc))
            {
                return string.Empty;
            }

            JObject service = JObject.Parse(serviceDoc);
            var entities = JsonParserHelper.GetEntries(service).ToList();

            if (entities == null)
            {
                return string.Empty;
            }

            foreach (JObject jo in entities)
            {
                if (jo["name"] != null && jo["name"].Value<string>().Equals(entitySetName)
                    && ((jo["kind"] != null && jo["kind"].Value<string>().Equals("EntitySet")) || jo["kind"] == null))
                {
                    return jo["url"] != null ? jo["url"].Value<string>() : string.Empty;
                }
            }

            return string.Empty;
        }

        /// <summary>
        /// Convert an entity set url to its name through service document.
        /// </summary>
        /// <param name="entitySetUrl">An entity set url.</param>
        /// <param name="serviceDoc">The service document.</param>
        /// <returns>Returns the related entity-set name.</returns>
        public static string ConvertToEntitySetNameUsingServiceDoc(this string entitySetUrl, string serviceDoc)
        {
            if (string.IsNullOrEmpty(entitySetUrl) || string.IsNullOrEmpty(serviceDoc))
            {
                return string.Empty;
            }

            JObject service = JObject.Parse(serviceDoc);
            var entities = JsonParserHelper.GetEntries(service).ToList();

            if (entities == null)
            {
                return string.Empty;
            }

            foreach (JObject jo in entities)
            {
                if (jo["url"] != null && jo["url"].Value<string>().Equals(entitySetUrl)
                    && ((jo["kind"] != null && jo["kind"].Value<string>().Equals("EntitySet")) || jo["kind"] == null))
                {
                    return jo["name"] != null ? jo["name"].Value<string>() : string.Empty;
                }
            }

            return string.Empty;
        }

        /// <summary>
        /// Convert an entity-type full name to its entity-set name.
        /// </summary>
        /// <param name="entityTypeName">An entity-type name.</param>
        /// <param name="metadataDoc">The metadata document.</param>
        /// <returns>Returns the related entity-set name.</returns>
        public static string ConvertToEntitySetNameUsingMetadataDoc(this string entityTypeName, string metadataDoc)
        {
            if (string.IsNullOrEmpty(entityTypeName) || string.IsNullOrEmpty(metadataDoc))
            {
                return string.Empty;
            }

            string entityTypeFullName = entityTypeName.AddNamespace(AppliesToType.EntityType, metadataDoc);
            XElement metadata = XElement.Parse(metadataDoc);
            string xpath = string.Format(@"//*[local-name()='Schema']/*[local-name()='EntityContainer']/*[local-name()='EntitySet' and @EntityType='{0}']", entityTypeFullName);
            var element = metadata.XPathSelectElement(xpath, ODataNamespaceManager.Instance);

            return element != null && element.Attribute("Name") != null ? element.Attribute("Name").Value : null;
        }

        /// <summary>
        /// Convert an entity-set name to its entity type full name.
        /// </summary>
        /// <param name="entitySetName">An entity-set name.</param>
        /// <param name="metadataDoc">The metadata document.</param>
        /// <returns>Returns the related entity type full name.</returns>
        public static string ConvertToEntityTypeFullName(this string entitySetName, string metadataDoc)
        {
            if (string.IsNullOrEmpty(entitySetName) || string.IsNullOrEmpty(metadataDoc))
            {
                return string.Empty;
            }

            XElement metadata = XElement.Parse(metadataDoc);
            string xpath = string.Format(@"//*[local-name()='Schema']/*[local-name()='EntityContainer']/*[local-name()='EntitySet' and @Name='{0}']", entitySetName);
            var element = metadata.XPathSelectElement(xpath, ODataNamespaceManager.Instance);

            return element != null && element.Attribute("EntityType") != null ? element.Attribute("EntityType").Value : null;
        }

        /// <summary>
        /// Convert an entity-set name to its entity-type name.
        /// </summary>
        /// <param name="entitySetName">An entity-set name.</param>
        /// <param name="metadataDoc">The metadata document.</param>
        /// <returns>Returns the entity-type short name.</returns>
        public static string ConvertToEntityTypeShortName(this string entitySetName, string metadataDoc)
        {
            if (string.IsNullOrEmpty(entitySetName) || string.IsNullOrEmpty(metadataDoc))
            {
                return string.Empty;
            }

            return entitySetName
                .ConvertToEntityTypeFullName(metadataDoc)
                .GetLastSegment();
        }

        /// <summary>
        /// Convert to navigation property's target.
        /// </summary>
        /// <param name="navigationPropertyPath">A navigation property path.</param>
        /// <param name="entitySetName">The entity-set name.</param>
        /// <param name="metadataDoc">The metadata document.</param>
        /// <returns>Returns the navigation property target.</returns>
        public static string ConvertToNavigationPropertyTarget(this string navigationPropertyPath, string entitySetName, string metadataDoc)
        {
            if (string.IsNullOrEmpty(navigationPropertyPath) ||
                string.IsNullOrEmpty(entitySetName) ||
                string.IsNullOrEmpty(metadataDoc))
            {
                return string.Empty;
            }

            string xPath =
                string.Format(
                "//*[local-name()='EntitySet' and @Name='{0}']/*[local-name()='NavigationPropertyBinding' and @Path='{1}']",
                entitySetName,
                navigationPropertyPath);
            var metadata = XElement.Parse(metadataDoc);
            var navigPropBinding = metadata.XPathSelectElement(xPath, ODataNamespaceManager.Instance);

            return null != navigPropBinding && null != navigPropBinding.Attribute("Target") ?
                navigPropBinding.GetAttributeValue("Target") :
                string.Empty;
        }
        
        /// <summary>
        /// Add namespace prefix for any names of the element in metadata document.
        /// </summary>
        /// <param name="targetName">The target element's name.</param>
        /// <param name="appliesType">The applies type of the element. (Related to the element's local-name.)</param>
        /// <param name="metadataDoc">The metadata document.</param>
        /// <returns>Returns the name with the namespace of the element.</returns>
        public static string AddNamespace(this string targetName, AppliesToType appliesType, string metadataDoc)
        {
            if (string.IsNullOrEmpty(targetName) || !metadataDoc.IsXmlPayload())
            {
                return string.Empty;
            }

            string target = targetName.GetLastSegment();
            XElement metadata = XElement.Parse(metadataDoc);
            string xpath = string.Format(@"//*[local-name()='{0}' and @Name='{1}']", appliesType, target);
            var element = metadata.XPathSelectElement(xpath, ODataNamespaceManager.Instance);

            if (null == element)
            {
                return string.Empty;
            }

            AliasNamespacePair aliasNamespace = element.GetAliasAndNamespace();

            if (string.IsNullOrEmpty(aliasNamespace.Namespace))
            {
                return string.Empty;
            }

            return string.Format("{0}.{1}", aliasNamespace.Namespace, target);
        }

        /// <summary>
        /// Judge whether the input name is an entity-set name.
        /// </summary>
        /// <param name="name">An input name.</param>
        /// <param name="metadataDoc">The metadata document.</param>
        /// <returns>Returns a decision outcome.</returns>
        public static bool IsEntitySetName(this string name, string metadataDoc)
        {
            if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(metadataDoc))
            {
                return false;
            }

            XElement metadata = XElement.Parse(metadataDoc);
            string xpath = @"//*[local-name()='Schema']/*[local-name()='EntityContainer']/*[local-name()='EntitySet']";
            var elements = metadata.XPathSelectElements(xpath, ODataNamespaceManager.Instance);
            var entitySetNames = elements.Where(ele => name == ele.Attribute("Name").Value).Select(ele => ele.Attribute("Name").Value);

            return 0 < entitySetNames.Count() ? true : false;
        }
        #endregion

        /// <summary>
        /// Gets all entity types which were satisfied specified conditions from metadata document. 
        /// </summary>
        /// <param name="metadataDoc">The metadata document.</param>
        /// <param name="entityTypePredicate">Assert whether an entity-type matches specified conditions.</param>
        /// <param name="containedKeyPropSum">The sum of the key properties in current entity-type.</param>
        /// <param name="containedKeyPropTypes">The contained key property types.</param>
        /// <param name="containedNorPropTypes">The contained normal property types.</param>
        /// <param name="containedNavigRoughType">The contained navigation properties' rough-type at least one was satisfied the input value.</param>
        /// <returns></returns>
        public static IEnumerable<EntityTypeElement> GetEntityTypes(
            string metadataDoc,
            uint? containedKeyPropSum = 1,
            IEnumerable<string> containedKeyPropTypes = null,
            IEnumerable<string> containedNorPropTypes = null,
            NavigationRoughType containedNavigRoughType = NavigationRoughType.None)
        {
            if (string.IsNullOrEmpty(metadataDoc))
            {
                return null;
            }

            List<EntityTypeElement> result = new List<EntityTypeElement>();
            XElement metadata = XElement.Parse(metadataDoc);
            string xpath = @"//*[local-name()='Schema']/*[local-name()='EntityType']";
            var elements = metadata.XPathSelectElements(xpath, ODataNamespaceManager.Instance);

            foreach (var ele in elements)
            {
                EntityTypeElement entityType = EntityTypeElement.Parse(ele);

                if (PredicateHelper.EntityTypeAnyPropertiesMeetsSpecifiedConditions(entityType, containedKeyPropSum, containedKeyPropTypes, containedNorPropTypes, containedNavigRoughType))
                {
                    result.Add(entityType);
                }
            }

            return result;
        }

        /// <summary>
        /// Gets a specified entity-type and parses it to the EntityTypeElement class type.
        /// </summary>
        /// <param name="entityTypeName">The entity-type name.</param>
        /// <param name="metadataDoc">The metadata document.</param>
        /// <returns>Returns the instance with the EntityTypeElement class type.</returns>
        public static EntityTypeElement GetSpecifiedEntityType(this string entityTypeName, string metadataDoc)
        {
            if (string.IsNullOrEmpty(entityTypeName) || !metadataDoc.IsXmlPayload())
            {
                return null;
            }

            string entityTypeShortName = entityTypeName.GetLastSegment();
            var metadata = XElement.Parse(metadataDoc);

            if (null == metadata)
            {
                return null;
            }

            string xPath = string.Format("//*[local-name()='EntityType' and @Name='{0}']", entityTypeShortName);
            var entityTypeElem = metadata.XPathSelectElement(xPath, ODataNamespaceManager.Instance);

            return null != entityTypeElem ? EntityTypeElement.Parse(entityTypeElem) : null;
        }

        /// <summary>
        /// Get key properties.
        /// </summary>
        /// <param name="metadataDoc">The metadata document.</param>
        /// <param name="entityTypeShortName">The short name of an entity type.</param>
        /// <returns>Returns all the key properties.</returns>
        public static IEnumerable<NormalProperty> GetKeyProperties(string metadataDoc, string entityTypeShortName)
        {
            if (string.IsNullOrEmpty(metadataDoc) || string.IsNullOrEmpty(entityTypeShortName))
            {
                return null;
            }

            XElement metadata = XElement.Parse(metadataDoc);
            string xpath = string.Format(@"//*[local-name()='Schema']/*[local-name()='EntityType' and @Name='{0}']", entityTypeShortName);
            var element = metadata.XPathSelectElement(xpath, ODataNamespaceManager.Instance);
            var entityType = EntityTypeElement.Parse(element);

            return entityType.NormalProperties.Where(np => np.IsKey).Select(np => np);
        }

        /// <summary>
        /// Get normal properties.
        /// </summary>
        /// <param name="metadataDoc">The metadata document.</param>
        /// <param name="entityTypeShortName">The short name of an entity type.</param>
        /// <returns>Returns all the normal properties.</returns>
        public static IEnumerable<NormalProperty> GetNormalProperties(string metadataDoc, string entityTypeShortName)
        {
            if (string.IsNullOrEmpty(metadataDoc) || string.IsNullOrEmpty(entityTypeShortName))
            {
                return null;
            }

            XElement metadata = XElement.Parse(metadataDoc);
            string xpath = string.Format(@"//*[local-name()='Schema']/*[local-name()='EntityType' and @Name='{0}']", entityTypeShortName);
            var element = metadata.XPathSelectElement(xpath, ODataNamespaceManager.Instance);
            var entityType = EntityTypeElement.Parse(element);

            return entityType.NormalProperties;
        }

        /// <summary>
        /// Get navigation properties.
        /// </summary>
        /// <param name="metadataDoc">The metadata document.</param>
        /// <param name="entityTypeShortName">The short name of an entity type.</param>
        /// <returns>Return all the navigation properties.</returns>
        public static IEnumerable<NavigProperty> GetNavigationProperties(string metadataDoc, string entityTypeShortName)
        {
            if (string.IsNullOrEmpty(metadataDoc) || string.IsNullOrEmpty(entityTypeShortName))
            {
                return null;
            }

            XElement metadata = XElement.Parse(metadataDoc);
            string xpath = string.Format(@"//*[local-name()='Schema']/*[local-name()='EntityType' and @Name='{0}']", entityTypeShortName);
            var element = metadata.XPathSelectElement(xpath, ODataNamespaceManager.Instance);
            var entityType = EntityTypeElement.Parse(element);

            return entityType.NavigationProperties;
        }

        /// <summary>
        /// Get a concurrency entity type from metadata document.
        /// </summary>
        /// <param name="metadataDoc">The OData metadata document.</param>
        /// <returns>Returns an entity-type.</returns>
        public static EntityTypeElement GetConcurrencyEntityType(string metadataDoc)
        {
            if (string.IsNullOrEmpty(metadataDoc))
            {
                return null;
            }

            XElement metadata = XElement.Parse(metadataDoc);
            string xpath = @"//*[local-name()='Schema']/*[local-name()='EntityType']/*[local-name()='Property' and @ConcurrencyMode='Fixed']";
            var element = metadata.XPathSelectElement(xpath, ODataNamespaceManager.Instance);

            if (null == element || null == element.Parent)
            {
                return null;
            }

            var entityType = EntityTypeElement.Parse(element.Parent);

            if (1 == entityType.KeyProperties.Count() && "Edm.Int32" == entityType.KeyProperties.First().PropertyType)
            {
                return entityType;
            }

            return null;
        }

        /// <summary>
        /// Get entity containing complex type with specified property type.
        /// </summary>
        /// <param name="metadata">The metadata document.</param>
        /// <param name="specifiedType">The specified type of property in complex type.</param>
        /// <param name="entityType">The entity type which containing complex type.</param>
        /// <param name="entityTypeInfo">The entity-type information.</param>
        /// <returns>true: get the specified entity; false: otherwise.</returns>
        public static bool GetEntityTypesWithComplexProperty(
            string metadataDoc,
            string specifiedType,
            out Dictionary<string, Dictionary<KeyValuePair<string, string>, List<string>>> entityTypeInfo)
        {
            entityTypeInfo = new Dictionary<string, Dictionary<KeyValuePair<string, string>, List<string>>>();
            var complexTypeInfo = new Dictionary<string, List<string>>();

            // Find all complex property containing property with specified type.
            XElement metadata = XElement.Parse(metadataDoc);
            string xPath = @"//*[local-name()='Schema']/*[local-name()='ComplexType']";
            var complexTypeElem = metadata.XPathSelectElements(xPath, ODataNamespaceManager.Instance);

            if (null == complexTypeElem || !complexTypeElem.Any())
            {
                return false;
            }

            foreach (var elem in complexTypeElem)
            {
                var propNames = from prop in elem.Elements()
                                where prop.Name.LocalName.Equals("Property") && prop.Attribute("Type").Value.Equals(specifiedType)
                                select prop.GetAttributeValue("Name");

                if (propNames.Any())
                {
                    complexTypeInfo.Add(elem.GetAttributeValue("Name"), propNames.ToList());
                }
            }

            if (null == complexTypeInfo || !complexTypeInfo.Any())
            {
                return false;
            }

            // Find all entity types containing got complex type
            xPath = @"//*[local-name()='Schema']/*[local-name()='EntityType']";
            var entityTypeElems = metadata.XPathSelectElements(xPath, ODataNamespaceManager.Instance);

            if (null == entityTypeElems || !entityTypeElems.Any())
            {
                return false;
            }

            foreach (XElement elem in entityTypeElems)
            {
                var complexTypes = new Dictionary<KeyValuePair<string, string>, List<string>>();

                foreach (var info in complexTypeInfo)
                {
                    string complexTypeName = info.Key;
                    var complexProperties =
                        from prop in elem.Elements()
                        where prop.Name.LocalName.Equals("Property") && prop.GetAttributeValue("Type").EndsWith(string.Format(".{0}", complexTypeName))
                        select prop;

                    if (null == complexProperties || !complexProperties.Any())
                    {
                        continue;
                    }

                    foreach (var cp in complexProperties)
                    {
                        if (info.Value.Any())
                        {
                            complexTypes.Add(new KeyValuePair<string, string>(cp.GetAttributeValue("Name"), cp.GetAttributeValue("Type")), info.Value);
                        }
                    }
                }

                if (complexTypes.Any())
                {
                    entityTypeInfo.Add(elem.GetAttributeValue("Name"), complexTypes);
                }
            }

            return null != entityTypeInfo && entityTypeInfo.Any();
        }

        /// <summary>
        /// Generate a individual property URL.
        /// </summary>
        /// <param name="metadata">The metadata document.</param>
        /// <param name="svcDoc">The service document.</param>
        /// <param name="propertyTypes">The specified property types.</param>
        /// <returns>The individual property URL.</returns>
        public static string GenerateIndividualPropertyURL(string metadata, string svcDoc, string svcDocUrl, List<string> propertyTypes)
        {
            string individualPropertyUrl = string.Empty;
            var payloadFormat = svcDoc.GetFormatFromPayload();
            string[] feeds = ContextHelper.GetFeeds(svcDoc, payloadFormat).ToArray();

            for (int i = 0; i < feeds.Length; i++)
            {
                string entitySetUrl = feeds[i];
                string entityTypeShortName = entitySetUrl.ConvertToEntitySetNameUsingServiceDoc(svcDoc).ConvertToEntityTypeShortName(metadata);
                List<string> properties = GetPropertiesWithSpecifiedTypeFromEntityType(entityTypeShortName, metadata, propertyTypes);

                if (properties.Count > 0)
                {
                    string feedUrl = svcDocUrl.TrimEnd('/') + @"/" + entitySetUrl + @"/?$top=1";
                    var response = WebHelper.Get(new Uri(feedUrl), Constants.V4AcceptHeaderJsonFullMetadata, RuleEngineSetting.Instance().DefaultMaximumPayloadSize, null);

                    if (response != null && response.StatusCode == HttpStatusCode.OK && !string.IsNullOrEmpty(response.ResponsePayload))
                    {
                        var entityIDs = ContextHelper.GetEntries(response.ResponsePayload, PayloadFormat.JsonLight);

                        if (entityIDs.Any())
                        {
                            string entityUrl = entityIDs.First();
                            individualPropertyUrl = entityUrl + @"/" + properties.First().Split(',')[0];
                            break;
                        }
                    }
                }
            }

            return individualPropertyUrl;
        }

        /// <summary>
        /// Generate URL of derived type.
        /// </summary>
        /// <param name="metadata">The metadata document.</param>
        /// <param name="svcDoc">The service document.</param>
        /// <returns>The URL of derived type.</returns>
        public static string GenerateDerivedTypeURL(string metadata, string svcDoc)
        {
            string derivedTypeUrl = string.Empty;
            //var payloadFormat = svcDoc.GetFormatFromPayload();
            //string[] feeds = ContextHelper.GetFeeds(svcDoc, payloadFormat).ToArray();

            var derivedElements = GetEntityTypeInheritance(metadata, svcDoc);

            if (derivedElements.Any())
            {
                EntityTypeElement ele = derivedElements.First();
                //string baseTypeShortName = ele.BaseTypeFullName.Contains('.') ? ele.BaseTypeFullName.Split('.')[1] : ele.BaseTypeFullName;
                string derivedTypeFullName = ele.EntityTypeFullName;
                string entitySetUrl = ele.BaseTypeFullName.ConvertToEntitySetUrl(metadata, svcDoc);
                derivedTypeUrl = entitySetUrl + @"/" + derivedTypeFullName;
            }

            return derivedTypeUrl;
        }

        /// <summary>
        /// Generate URL of media stream property.
        /// </summary>
        /// <param name="metadata">The metadata document.</param>
        /// <param name="svcDoc">The service document.</param>
        /// <param name="svcDocUrl">The service document URL.</param>
        /// <returns>The URL of media stream property.</returns>
        public static string GenerateMediaStreamURL(string metadata, string svcDoc, string svcDocUrl)
        {
            string mediaStreamUrl = string.Empty;
            var payloadFormat = svcDoc.GetFormatFromPayload();
            var feeds = ContextHelper.GetFeeds(svcDoc, payloadFormat);

            foreach (string feed in feeds)
            {
                string entityTypeShortName = feed.ConvertToEntitySetNameUsingServiceDoc(svcDoc).ConvertToEntityTypeShortName(metadata);
                List<string> properties = GetPropertiesWithSpecifiedTypeFromEntityType(entityTypeShortName, metadata, new List<string>() { PrimitiveDataTypes.Stream });

                if (properties.Count > 0)
                {
                    string entityUrl = string.Empty;
                    string feedUrl = svcDocUrl.TrimEnd('/') + @"/" + feed + @"/?$top=1";
                    var response = WebHelper.Get(new Uri(feedUrl), Constants.V4AcceptHeaderJsonFullMetadata, RuleEngineSetting.Instance().DefaultMaximumPayloadSize, null);

                    if (response != null && response.StatusCode == HttpStatusCode.OK && !string.IsNullOrEmpty(response.ResponsePayload))
                    {
                        var entityIDs = ContextHelper.GetEntries(response.ResponsePayload, PayloadFormat.JsonLight);

                        if (entityIDs.Any())
                        {
                            entityUrl = entityIDs.First();
                            mediaStreamUrl = entityUrl + @"/" + properties.First().Split(',')[0];
                            break;
                        }
                    }
                }
            }

            return mediaStreamUrl;
        }

        /// <summary>
        /// Generate a reference URL according to metadata and service document.
        /// </summary>
        /// <param name="metadata">The metadata document.</param>
        /// <param name="svcDoc">The service document.</param>
        /// <param name="svcDocUrl">The service document URL.</param>
        /// <returns></returns>
        public static string GenerateReferenceURL(string metadata, string svcDoc, string svcDocUrl)
        {
            string referenceURL = string.Empty;
            var payloadFormat = svcDoc.GetFormatFromPayload();
            var feeds = ContextHelper.GetFeeds(svcDoc, payloadFormat);

            foreach (string feed in feeds)
            {
                string feedUrl = svcDocUrl.TrimEnd('/') + @"/" + feed + @"/?$top=1";
                var response = WebHelper.Get(new Uri(feedUrl), Constants.V4AcceptHeaderJsonFullMetadata, RuleEngineSetting.Instance().DefaultMaximumPayloadSize, null);

                if (response != null && response.StatusCode == HttpStatusCode.OK && !string.IsNullOrEmpty(response.ResponsePayload))
                {
                    JObject jo;
                    response.ResponsePayload.TryToJObject(out jo);
                    var entities = JsonParserHelper.GetEntries(jo);

                    if (entities != null && entities.Count > 0)
                    {
                        foreach (JProperty jp in entities.First())
                        {
                            if (jp.Name.EndsWith(Constants.OdataAssociationLinkPropertyNameSuffix)
                                && jp.Value.ToString().StripOffDoubleQuotes().EndsWith("$ref"))
                            {
                                referenceURL = Uri.IsWellFormedUriString(jp.Value.ToString().StripOffDoubleQuotes(), UriKind.Relative) ?
                                    svcDocUrl.TrimEnd('/') + @"/" + jp.Value.ToString().StripOffDoubleQuotes() :
                                    jp.Value.ToString().StripOffDoubleQuotes();
                                break;
                            }
                        }
                    }
                }

                if (!string.IsNullOrEmpty(referenceURL))
                {
                    break;
                }
            }

            return referenceURL;
        }

        /// <summary>
        /// Get all entity set names from entity container
        /// </summary>
        /// <param name="metadata">The metadata document.</param>
        /// <returns>The entity set names.</returns>
        public static List<string> GetAllEntitySetNames(string metadata)
        {
            List<string> entitySetNames = new List<string>();

            XElement metaXel = XElement.Parse(metadata);
            string xpath = @"//*[local-name()='EntityContainer']/*[local-name()='EntitySet']";
            var elements = metaXel.XPathSelectElements(xpath, ODataNamespaceManager.Instance);

            if (elements.Any())
            {
                foreach (XElement element in elements)
                {
                    entitySetNames.Add(element.GetAttributeValue("Name"));
                }
            }

            return entitySetNames;
        }

        /// <summary>
        /// Get EntityContainer names of specified metadata document.
        /// </summary>
        /// <param name="metadata">The metadata document.</param>
        /// <returns>The names of EntityContainer.</returns>
        public static List<string> GetEntityContainerNames(string metadata)
        {
            List<string> entityContainerNames = new List<string>();

            XElement metaXel = XElement.Parse(metadata);
            string xpath = @"//*[local-name()='EntityContainer']";
            var elements = metaXel.XPathSelectElements(xpath, ODataNamespaceManager.Instance);

            if (elements.Any())
            {
                foreach (XElement element in elements)
                {
                    entityContainerNames.Add(element.GetAttributeValue("Name"));
                }
            }

            return entityContainerNames;
        }

        /// <summary>
        /// Get all entity set names from entity container
        /// </summary>
        /// <param name="metadata">The metadata document.</param>
        /// <returns>The entity set names.</returns>
        public static List<string> GetEntityTypeNamesOfAllEntityset(string metadata)
        {
            List<string> entityTypeNames = new List<string>();

            XElement metaXel = XElement.Parse(metadata);
            string xpath = @"//*[local-name()='EntityContainer']/*[local-name()='EntitySet']";
            var elements = metaXel.XPathSelectElements(xpath, ODataNamespaceManager.Instance);

            if (elements.Any())
            {
                foreach (XElement element in elements)
                {
                    entityTypeNames.Add(element.GetAttributeValue("EntityType").GetLastSegment());
                }
            }

            return entityTypeNames;
        }

        /// <summary>
        /// Get all the feeds from the metadata document.
        /// </summary>
        /// <param name="metadataDoc">The metadata document.</param>
        /// <returns>Returns all the feeds.</returns>
        public static IEnumerable<string> GetFeeds(string metadataDoc)
        {
            if (string.IsNullOrEmpty(metadataDoc) || !metadataDoc.IsXmlPayload())
            {
                return new List<string>();
            }

            List<string> feeds = new List<string>();
            var metadata = XElement.Parse(metadataDoc);
            string xPath = "//*[local-name()='EntitySet']";
            var eles = metadata.XPathSelectElements(xPath, ODataNamespaceManager.Instance);

            if (null == eles || !eles.Any())
            {
                return new List<string>();
            }

            foreach (var ele in eles)
            {
                if (null == ele.Attribute("Name"))
                {
                    continue;
                }

                feeds.Add(ele.GetAttributeValue("Name"));
            }

            return feeds;
        }

        /// <summary>
        /// Gets the alias and namespace of an element in metadata.
        /// </summary>
        /// <param name="element">The target element which will be got the alias and namespace.</param>
        /// <returns>Returns the alias and namespace information and store them in an parameter with AliasNamespacePair type.</returns>
        public static AliasNamespacePair GetAliasAndNamespace(this XElement element)
        {
            if (null == element)
            {
                throw new ArgumentNullException("The input parameter 'element' cannot be null");
            }

            string alias = string.Empty, nspace = string.Empty;
            string ns = element.Name.NamespaceName;

            while (null != element.Parent && ns == element.Parent.Name.NamespaceName)
            {
                element = element.Parent;
            }

            alias = null != element.Attribute("Alias") ? element.Attribute("Alias").Value : string.Empty;
            nspace = null != element.Attribute("Namespace") ? element.Attribute("Namespace").Value : string.Empty;

            return new AliasNamespacePair(alias, nspace);
        }

        /// <summary>
        /// Get the string list of the enumeration member names.
        /// </summary>
        /// <param name="metadatadoc">The string of the metadata document.</param>
        /// <param name="enumType">The name of the enumeration type.</param>
        /// <returns>The string list of the enumeration memeber names.</returns>
        public static List<string> GetValuesOfAnEnum(string metadatadoc, string enumType)
        {
            List<string> result = new List<string>();
            XElement metaXml = XElement.Parse(metadatadoc);
            string xpath = string.Format(@"//*[local-name()='EnumType' and @Name='{0}']", enumType);
            XElement element = metaXml.XPathSelectElement(xpath, ODataNamespaceManager.Instance);

            if (element != null)
            {
                foreach (XElement member in element.Descendants("Member"))
                {
                    result.Add(member.Attribute("Name").Value);
                }
            }

            return result;
        }

        /// <summary>
        /// Tells whehter a type is an enumeration type.
        /// </summary>
        /// <param name="metadatadoc">The string of the metadata document.</param>
        /// <param name="enumType">The name of the enumeratin type.</param>
        /// <returns>True, if it is an enum; false, otherwise.</returns>
        public static bool IsEnumType(string metadatadoc, string enumType)
        {
            XElement metaXml = XElement.Parse(metadatadoc);
            string xpath = string.Format(@"//*[local-name()='EnumType' and @Name='{0}']", enumType);
            XElement element = metaXml.XPathSelectElement(xpath, ODataNamespaceManager.Instance);

            if (element != null && element.Attribute("Name").Value.Equals(enumType))
                return true;

            return false;
        }
        
        /// <summary>
        /// Verify whether the entity is a media-type entity or not.
        /// </summary>
        /// <param name="entityTypeShortName">The entity-type short name.</param>
        /// <returns>Returns a boolean value: true or false.</returns>
        public static bool IsMediaType(this string entityTypeShortName)
        {
            if (!entityTypeShortName.IsSpecifiedEntityTypeShortNameExist())
            {
                return false;
            }

            var metadata = XElement.Parse(ServiceStatus.GetInstance().MetadataDocument);
            var xPath = string.Format("//*[local-name()='EntityType' and @Name='{0}' and @HasStream='true']/*[local-name()='Annotation' and @Term='Org.OData.Core.V1.AcceptableMediaTypes']", entityTypeShortName);
            var xElem = metadata.XPathSelectElement(xPath, ODataNamespaceManager.Instance);

            return null != xElem;
        }
    }
}
